home *** CD-ROM | disk | FTP | other *** search
/ MacAddict 108 / MacAddict108.iso / Software / Internet & Communication / JunkMatcher 1.5.5.dmg / JunkMatcher.app / Contents / Resources / Engine / HTMLEncoding.py < prev    next >
Encoding:
Python Source  |  2005-06-01  |  9.9 KB  |  251 lines

  1. #
  2. #  HTMLEncoding.py
  3. #  JunkMatcher
  4. #
  5. #  Created by Benjamin Han on 2/1/05.
  6. #  Copyright (c) 2005 Benjamin Han. All rights reserved.
  7. #
  8.  
  9. # This program is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU General Public License
  11. # as published by the Free Software Foundation; either version 2
  12. # of the License, or (at your option) any later version.
  13.  
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. # GNU General Public License for more details.
  18.  
  19. # You should have received a copy of the GNU General Public License
  20. # along with this program; if not, write to the Free Software
  21. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  22.  
  23. #!/usr/bin/env python
  24.  
  25. # IMPORTANT: both HTMLEncodingExtractor and HTMLFormatter can have only a single instance!
  26.  
  27. from sgmllib import SGMLParser
  28. import threading  # to ensure thread-safety, since we will have only one
  29.                   # global HTMLEncodingExtractor and HTMLFormatter
  30.  
  31. from consts import *
  32. from utilities import *
  33. import htmlentitydefs
  34.  
  35.  
  36. _metaCharsetPat = re.compile(r'charset\s*=\s*([^\s"\'>]*)')
  37.  
  38. # there is no danger of reentrant locking, so we don't use RLock here
  39. _htmlEncodingExtractorLock = threading.Lock()
  40. _htmlFormatterLock = threading.Lock()
  41.  
  42.  
  43. class HTMLEncodingExtractor (SGMLParser):
  44.     """
  45.     A SGMLParser-derived parser to extract web page encoding
  46.     --------------------------------------------------------
  47.     hasTagHTML: True iff the given src has <HTML> tag
  48.     hasTagHead: True iff the given src has <HEAD> tag
  49.     
  50.     Call exctract(src) to extract the encoding.
  51.     """
  52.     def reset (self):
  53.         SGMLParser.reset(self)
  54.         self.encoding = None
  55.         self._tagStack = []
  56.         self.hasTagHTML = self.hasTagHead = False
  57.  
  58.     def unknown_starttag(self, tag, attributes):
  59.         if tag == 'html': self.hasTagHTML = True
  60.         elif tag == 'head': self.hasTagHead = True
  61.         self._tagStack.append(tag)
  62.     def unknown_endtag(self, tag):        
  63.         if len(self._tagStack) and self._tagStack[-1] == tag:
  64.             del self._tagStack[-1:]
  65.     
  66.     def do_meta (self, attrs):
  67.         if len(self._tagStack) >= 2:
  68.             if self._tagStack[-1] == 'head' and self._tagStack[-2] == 'html':
  69.                 self._extractEncoding(attrs)
  70.         elif len(self._tagStack) == 1 and self._tagStack[-1] == 'head':
  71.             self._extractEncoding(attrs)
  72.         elif len(self._tagStack) == 0:
  73.             self._extractEncoding(attrs)
  74.  
  75.     def _extractEncoding (self, attrs):
  76.         attrDict=dict(attrs)
  77.         httpEquiv = attrDict.get('http-equiv')
  78.         if httpEquiv and httpEquiv.lower() == 'content-type':
  79.             content = attrDict['content']
  80.             if content:
  81.                 mo = _metaCharsetPat.search(content)
  82.                 if mo:
  83.                     self.encoding = mo.group(1)
  84.                     self.setnomoretags()
  85.  
  86.     def extract (self, htmlSrc):
  87.         """Extract the encoding from htmlSrc; returns the encoding (could be None)"""
  88.         # thread-safety: multiple threads may call extract() and access the ivars simultaneously
  89.         _htmlEncodingExtractorLock.acquire()
  90.  
  91.         try:
  92.             self.reset()
  93.             self.feed(htmlSrc)
  94.             self.close()
  95.             encoding = self.encoding
  96.             
  97.         except Exception, e:
  98.             printException(u'Exception in HTMLEncodingExtractor.extract()', e)
  99.             encoding = None
  100.             
  101.         _htmlEncodingExtractorLock.release()
  102.         
  103.         return encoding
  104.  
  105.  
  106. class HTMLFormatter (SGMLParser):
  107.     """
  108.     A SGMLParser-derived parser to rewrite web page encoding into utf-8
  109.     -------------------------------------------------------------------
  110.     Call format(src) to get the modified HTML src.
  111.     """
  112.     def reset(self):
  113.         # extend (called by SGMLParser.__init__)
  114.         SGMLParser.reset(self)
  115.         self._pieces = []
  116.         self._tagStack = []
  117.         self.encoding = None
  118.         self.insertCharset = 0
  119.         
  120.     def unknown_starttag(self, tag, attrs):
  121.         # called for each start tag
  122.         # attrs is a list of (attr, value) tuples
  123.         # e.g. for <pre class="screen">, tag="pre", attrs=[("class", "screen")]
  124.         # Ideally we would like to reconstruct original tag and attributes, but
  125.         # we may end up quoting attribute values that weren't quoted in the source
  126.         # document, or we may change the type of quotes around the attribute value
  127.         # (single to double quotes).
  128.         # Note that improperly embedded non-HTML code (like client-side Javascript)
  129.         # may be parsed incorrectly by the ancestor, causing runtime script errors.
  130.         # All non-HTML code must be enclosed in HTML comment tags (<!-- code -->)
  131.         # to ensure that it will pass through this parser unaltered (in handle_comment).
  132.         strattrs = ''.join([' %s="%s"' % (key, value) for key, value in attrs])
  133.         self._pieces.append('<%(tag)s%(strattrs)s>' % locals())
  134.         if tag == 'html':
  135.             if self.insertCharset == 2:
  136.                 self._pieces.append('\n<head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head>')
  137.         elif tag == 'head':
  138.             if self.insertCharset == 3:
  139.                 self._pieces.append('\n<meta http-equiv="content-type" content="text/html; charset=utf-8">')
  140.             
  141.         self._tagStack.append(tag)
  142.         
  143.     def unknown_endtag(self, tag):
  144.         # called for each end tag, e.g. for </pre>, tag will be "pre"
  145.         # Reconstruct the original end tag.
  146.         self._pieces.append('</%(tag)s>' % locals())
  147.         if len(self._tagStack) and self._tagStack[-1] == tag:
  148.             del self._tagStack[-1:]
  149.  
  150.     def do_meta (self, attrs):
  151.         if len(self._tagStack) >= 2:
  152.             if self._tagStack[-1] == 'head' and self._tagStack[-2] == 'html':
  153.                 self._rewriteEncoding(attrs)
  154.             else:
  155.                 self.unknown_starttag('meta', attrs)
  156.         elif len(self._tagStack) == 1 and self._tagStack[-1] == 'head':
  157.             self._rewriteEncoding(attrs)
  158.         elif len(self._tagStack) == 0:
  159.             self._rewriteEncoding(attrs)
  160.         else:
  161.             self.unknown_starttag('meta', attrs)
  162.  
  163.     def _rewriteEncoding (self, attrs):        
  164.         attrDict = dict(attrs)
  165.         httpEquiv = attrDict.get('http-equiv')
  166.         if httpEquiv and httpEquiv.lower() == 'content-type':
  167.             content = attrDict['content']
  168.             if content:
  169.                 mo = _metaCharsetPat.search(content)
  170.                 if mo:                    
  171.                     self.encoding = mo.group(1)
  172.                     attrDict['content'] = '%sutf-8%s' % (content[:mo.start(1)], content[mo.end(1):])
  173.  
  174.         strattrs = ''.join([' %s="%s"' % (key, value) for key, value in attrDict.items()])
  175.         self._pieces.append('<meta%s>' % strattrs)
  176.         # NOTE we do not push 'meta' into _tagStack - cuz multiple meta would sabotage parsing!
  177.         
  178.     def handle_charref(self, ref):
  179.         # called for each character reference, e.g. for " ", ref will be "160"
  180.         # Reconstruct the original character reference.
  181.         self._pieces.append('&#%(ref)s;' % locals())
  182.         
  183.     def handle_entityref(self, ref):
  184.         # called for each entity reference, e.g. for "©", ref will be "copy"
  185.         # Reconstruct the original entity reference.
  186.         self._pieces.append('&%(ref)s' % locals())
  187.         # standard HTML entities are closed with a semicolon; other entities are not
  188.         if htmlentitydefs.entitydefs.has_key(ref):
  189.             self._pieces.append(';')
  190.  
  191.     def handle_data(self, text):
  192.         # called for each block of plain text, i.e. outside of any tag and
  193.         # not containing any character or entity references
  194.         # Store the original text verbatim.
  195.         self._pieces.append(text)
  196.         
  197.     def handle_comment(self, text):
  198.         # called for each HTML comment, e.g. <!-- insert Javascript code here -->
  199.         # Reconstruct the original comment.
  200.         # It is especially important that the source document enclose client-side
  201.         # code (like Javascript) within comments so it can pass through this
  202.         # processor undisturbed; see comments in unknown_starttag for details.
  203.         self._pieces.append('<!--%(text)s-->' % locals())
  204.         
  205.     def handle_pi(self, text):
  206.         # called for each processing instruction, e.g. <?instruction>
  207.         # Reconstruct original processing instruction.
  208.         self._pieces.append('<?%(text)s>' % locals())
  209.  
  210.     def handle_decl(self, text):
  211.         # called for the DOCTYPE, if present, e.g.
  212.         # <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  213.         #     "http://www.w3.org/TR/html4/loose.dtd">
  214.         # Reconstruct original DOCTYPE
  215.         self._pieces.append('<!%(text)s>' % locals())
  216.         
  217.     def format(self, src, insertCharset, hasTagHTML, hasTagHead):
  218.         """Return formatted HTML as a single string"""
  219.         # thread-safety: multiple threads may call extract() and access the ivars simultaneously
  220.         _htmlFormatterLock.acquire()
  221.  
  222.         try:
  223.             self.reset()
  224.  
  225.             # self.insertCharset
  226.             # 0: don't insert charset meta tag
  227.             # 1: insert on the top
  228.             # 2: insert right after <HTML>
  229.             # 3: insert right after <HEAD>
  230.  
  231.             if insertCharset:
  232.                 if hasTagHead: self.insertCharset = 3
  233.                 elif hasTagHTML: self.insertCharset = 2
  234.                 else: self.insertCharset = 1
  235.  
  236.             self.feed(src)
  237.             self.close()
  238.  
  239.             if self.insertCharset == 1:
  240.                 ret = '<meta http-equiv="content-type" content="text/html; charset=utf-8">\n%s' % ''.join(self._pieces)
  241.             else:
  242.                 ret = ''.join(self._pieces)
  243.  
  244.         except Exception, e:            
  245.             printException(u'Exception in HTMLFormatter.extract()', e)
  246.             ret = ''
  247.  
  248.         _htmlFormatterLock.release()
  249.  
  250.         return ret
  251.